﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using PluginInterface;
using SdmxMl.Common;
using SdmxMl.Structure;

namespace SdmxMl.Manager
{
    /// <summary>  SMS Plug-in managment </summary>
    public class PluginManager
    {
        #region Constants

        public const string FULL_PLUG_LIST_FILENAME = "CL_FULL_PLUGIN.xml";
        private const string CURRENT_PLUG_LIST_FILENAME = "CL_SAE_PLUGIN.xml";
        public const string PLUG_IN_SUBDIR_NAME = "Plugin";
        public const string PLUG_IN_INSTALLER_ID = "PLUG-IN";

        #endregion

        #region Internal Members

        /// <summary>Place holder for Plug In</summary>
        private TabControl tabControlPlugin;

        /// <summary>Main tree manager</summary>
        private TreeSdmxManager treeManager;

        /// <summary>Load at start Plug-in list represented as SDMX codelist</summary>
        private CodeListType cltCurrent;
        
        /// <summary>Plug in sub directory name </summary>
        public string PluginBaseDir { get; private set; }

        /// <summary>Plug in sub directory name </summary>
        private Dictionary<string, UserControl> dicoPlugInstances;

        #endregion

        #region Properties

        /// <summary>Full Plug-in list represented as SDMX codelist</summary>
        public CodeListType CltPlugFullList { get; private set; }

        #endregion

        #region Constructor

        /// <summary>Constructor</summary>
        /// <param name="tcPlugin">place holder Tab control </param>
        /// <param name="tm">Main tree manager</param>
        public PluginManager(TabControl tcPlugin, TreeSdmxManager tm)
        {
            tabControlPlugin = tcPlugin;
            treeManager = tm;
            dicoPlugInstances = new Dictionary<string, UserControl>();
        }
        #endregion

        #region Initial loag according to current Plug list

        /// <summary> Process Initial load during application start</summary>
        public void InitialLoadPlugin()
        {
            // If possible and not yet done
            if (treeManager != null && tabControlPlugin != null && tabControlPlugin.TabPages.Count == 0)
            {
                // Fetch înformation about DLL to load
                SmManager sm = new SmManager();
                PluginBaseDir = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
                PluginBaseDir += Path.DirectorySeparatorChar + PLUG_IN_SUBDIR_NAME;
                if (Directory.Exists(PluginBaseDir))
                {
                    cltCurrent = LoadMetaPlugIn(CURRENT_PLUG_LIST_FILENAME);
                    CltPlugFullList = LoadMetaPlugIn(FULL_PLUG_LIST_FILENAME);

                    // Install all plug-in of current meta list
                    if (cltCurrent != null && CltPlugFullList != null)
                        foreach (CodeType c in cltCurrent.CodeList)
                            ProcessPlugIn(c, false);
                }

                // Add event on Tab change
             //   tabControlPlugin.SelectedIndexChanged += new EventHandler(tabControlPlugin_SelectedIndexChanged);
                treeManager.TV.AfterSelect += new TreeViewEventHandler(TV_AfterSelect);
            }
        }
        #endregion

        #region Load Meta list of Plug-in

        /// <summary>
        /// Load plug-in description embedded in Codelist maintenable artefact
        /// </summary>
        /// <param name="metaFileName">SDMX codelist filename</param>
        /// <returns>Instance of Codelist</returns>
        public CodeListType LoadMetaPlugIn(string metaFileName)
        {
            bool loaded = false;
            CodeListType codelistMetaPlug = null;

            // Fetch înformation about DLL to load
            SmManager sm = new SmManager();
            if (Directory.Exists(PluginBaseDir))
            {
                try
                {
                    sm.LoadSdmxDocument(PluginBaseDir + Path.DirectorySeparatorChar + metaFileName, true, null);
                    loaded = true;
                }
                catch { }
                if (loaded)
                {
                    ArtefactManager am = sm.GetArtefactManager(SdmxArtefactType.CodeLists);
                    if (am.ArtefactList.Count == 1)
                        codelistMetaPlug = am.ArtefactList[0] as CodeListType;
                }
            }

            return codelistMetaPlug;
        }
        #endregion

        #region Plug-in Dll alternate dir retrieval

        /// <summary>
        /// As it is possible to specify an alternate path for the dll, fetch it if exists
        /// </summary>
        /// <param name="anDir">annotation holding optional dll path</param>
        /// <returns>dicectory of dll location</returns>
        private string GetPluginDir(Annotation anDir)
        {
            string dir = PluginBaseDir;
            if (anDir != null && string.IsNullOrEmpty(anDir.Title) == false && anDir.Title.Length > 1)
            {
                if (anDir.Title[anDir.Title.Length - 1] == Path.DirectorySeparatorChar ||
                    anDir.Title[anDir.Title.Length - 1] == Path.AltDirectorySeparatorChar)
                {
                    dir = anDir.Title.Substring(0, anDir.Title.Length - 1);
                }
                else
                    dir = anDir.Title;

                if (Directory.Exists(dir) == false)
                    dir = PluginBaseDir;

            }

            return dir;
        }
        #endregion

        #region Check If plug in is installed as Tab page

        /// <summary>Check If plug in is installed as Tab page</summary>
        /// <param name="plugInId">Plug-in Id is also Tab id</param>
        /// <returns>True if present as tab page</returns>
        public bool PlugInControlLoaded(string plugInId)
        {
           return tabControlPlugin != null && tabControlPlugin.TabPages.ContainsKey(plugInId);
        }
        #endregion

        #region Remove installed Plug-in

        /// <summary>Removes installed Plug-in from tabbed view</summary>
        /// <param name="plugInId">Plug-in Id is also Tab id</param>
        public void RemovePlugIn(string plugInId)
        {
            if (PlugInControlLoaded(plugInId))
            {
                TabPage tp = tabControlPlugin.TabPages[plugInId];
                // Detach plug user control
                tp.Controls.Remove(dicoPlugInstances[plugInId]);

                // and remove tab
                tabControlPlugin.TabPages.Remove(tp);
            }
        }
        #endregion

        #region Add Plug-in

        /// <summary>
        /// Process adding plug-in in tabbed view.
        /// </summary>
        /// <param name="codePluginfo">Meta info for Plug-in Dll  held in code object</param>
        /// <param name="addAtStart">If true add tab with plug-in as first tab.</param>
        public void ProcessPlugIn(CodeType codePluginfo, bool addAtStart)
        {
            // Do nothing if yet installed or nothing specified
            if (codePluginfo == null || tabControlPlugin.TabPages.ContainsKey(codePluginfo.Id))
                return;

            if (dicoPlugInstances.ContainsKey(codePluginfo.Id) == false)
            {
                Annotation anDll = codePluginfo.AnnotationList.GetAnnotationType("DLL");
                Annotation anClass = codePluginfo.AnnotationList.GetAnnotationType("CLASS");
                Annotation anDir = codePluginfo.AnnotationList.GetAnnotationType("DLL_DIR");

                if (anDll != null && string.IsNullOrEmpty(anDll.Title) == false &&
                    anClass != null && string.IsNullOrEmpty(anClass.Title) == false)
                {
                    string dir = GetPluginDir(anDir);
                    string pluginPath = dir + Path.DirectorySeparatorChar + anDll.Title;

                    // Create plugin instance in main domain
                    object obj;
                    obj = AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(pluginPath, anClass.Title);
                    UserControl uc = ((IPlugin)obj).VisualComponent();
                    ((IPlugin)obj).PluginEventExposed += new PluginEvent(PluginEventExposed);
                    uc.Dock = DockStyle.Fill;

                    // add in dictionary
                    dicoPlugInstances.Add(codePluginfo.Id, uc);
                }

            }
            // Add plugin as tabpage
            TabPage tp = new TabPage(codePluginfo.Id);
            tp.Controls.Add(dicoPlugInstances[codePluginfo.Id]);
            tp.Name = codePluginfo.Id;
            if (addAtStart)
                tabControlPlugin.TabPages.Insert(0, tp);
            else 
                tabControlPlugin.TabPages.Add(tp);

        }

        #endregion

        #region Inform Plug-in of tree selection

        void TV_AfterSelect(object sender, TreeViewEventArgs e)
        {
            tabControlPlugin_SelectedIndexChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Some plug-in may require some information when main selection change.
        /// Informs currently selected in tabbed view
        /// </summary>
        /// <param name="sender"></param> <param name="e"></param>
        void tabControlPlugin_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Fetch IPlugin
            if (tabControlPlugin != null && tabControlPlugin.SelectedTab != null)
            {
                try
                {
                    IPlugin ip = tabControlPlugin.SelectedTab.Controls[0] as IPlugin;
                    if (ip != null && ip.RequiresInit)
                    {
                        XmlDocument xDoc = null;
                        if (treeManager.TV != null && treeManager.TV.SelectedNode != null)
                        {
                            Artefact a = treeManager.TV.SelectedNode.Tag as Artefact;
                            if (a != null)
                            {
                                ArtefactManager arm = treeManager.TV.SelectedNode.Parent.Tag as ArtefactManager;
                                if (arm != null)
                                    xDoc = arm.GetAsDocument(a);
                            }
                        }
                        ip.Init(xDoc, null);
                    }
                }
                catch { }
            }
        }
        #endregion

        #region Plugin Event requires adding sdmx on main tree

        void PluginEventExposed(object sender, PluginEventArgs e)
        {
            XmlDocument xDoc = e.DocSdmxMl;
            if (xDoc != null)
                treeManager.LoadSdmxArtefact(null, true, xDoc);
        }

        #endregion

        #region Persist as default Meta list

        /// <summary>
        /// Create a startup list for application. 
        /// This list reflects the plug.in tabs installed.
        /// </summary>
        public void PersistSelectionAsDefaultMetaList()
        {
            // Pre condition, full list available
            if (CltPlugFullList == null)
                return;

            // Scan installed tabs and create meta codelist 
            CodeListType cltDefault = new CodeListType();
            cltDefault.Id = "CL_PLUGIN_DEF";
            cltDefault.AgencyId = "NSI";
            cltDefault.Name.UpdateDefaultText("Plug-in list to install at application start");

            // Scans installed plug-in
            foreach (TabPage tp in tabControlPlugin.TabPages)
            {
                CodeType c = CltPlugFullList.CodeList.LocateItem(tp.Name) as CodeType;
                if (c != null)
                {
                    cltDefault.CodeList.Add(c);
                }
            }

            // Now persist default list
            SmManager sm = new SmManager();
            sm.PutArtefact(SdmxArtefactType.CodeLists, cltDefault);
            XmlDocument xDoc = sm.GetAsDocument();
            xDoc.Save(PluginBaseDir + Path.DirectorySeparatorChar + CURRENT_PLUG_LIST_FILENAME);
        }

        #endregion
    }
}
